use std::{cmp::min, collections::BTreeSet, fs, path::Path, u64};

use rbatis::{rbdc::DateTime, Error};
use rbs::Value;
use regex::Regex;

use crate::{
    config::AppContext,
    domain::{self, reply::Reply, topic::Topic},
    utils::{self, data_dir, image_dir},
    FILE_PATH, STATIC_PATH,
};

use super::search_service;
pub async fn save(topic: &mut Topic) -> Result<u64, Error> {
    if topic.sum.as_ref().map_or(false, |f| f.is_empty()) {
        unescap_sum(topic);
    }
    let rb = AppContext::rbatis();
    topic.atime = Some(DateTime::now());
    topic.ist = Some(0);
    let exec_result = Topic::insert(rb, topic).await?;
    let res = match exec_result.last_insert_id {
        Value::U64(id) => {
            topic.id = Some(id);
            Ok(id)
        }
        _ => Err(Error::E(String::from("error"))),
    };
    rname_imgs(topic);
    let _ = Topic::update_by_column(rb, &topic, "id").await?;
    search_service::save_document(topic).unwrap();
    res
}

//把上传到临时目录的文件移动id文件夹下，temp目录会有定时任务清理。返回是有效的图片名称
pub fn rname_imgs(topic: &mut Topic) -> Vec<String> {
    let data_dir = data_dir();
    let t = "/temp/";
    let mut urls: Vec<String> = Vec::new();
    let mut content = topic.content.clone().unwrap();
    if topic.style == Some(1) {
        content = markdown::to_html(&content);
    }
    let image_path = format!("/{}/{}/", STATIC_PATH, FILE_PATH);
    let re = Regex::new(r#"<img[^>]*src=["']([^"']*)["'][^>]*>"#).unwrap();
    for cap in re.captures_iter(&content.clone()) {
        if let Some(src) = cap.get(1) {
            let src = src.as_str().trim();
            if !src.starts_with(&image_path){
                continue;
            }
            let _new = src.replace("/temp/", format!("/{}/", topic.id.unwrap()).as_str());
            content = content.replace(src, &_new);
            let var_name = &format!("{}{}", data_dir, src);
            let old_file = Path::new(var_name.as_str());
            let var_name = &format!("{}{}", data_dir, _new);
            let new_file = Path::new(var_name.as_str());
            let parent = new_file.parent().unwrap();
            if !parent.exists() {
                std::fs::create_dir_all(parent).unwrap();
            }
            fs::rename(old_file, new_file).unwrap();
            urls.push(new_file.file_name().unwrap().to_str().unwrap().to_string());
        }
    }
    if topic.style == Some(2) {
        //html
        topic.content = Some(content);
    } else {
        //md
        let mut md = topic.content.clone().unwrap_or_default();
        urls.iter().for_each(|v| {
            md = md.replace(
                format!("/images/temp/{}", v).as_str(),
                format!("/images/{}/{}", topic.id.unwrap(), v).as_str(),
            );
        });
        topic.content = Some(md);
    }

    //处理缩略图
    if topic.pic.as_ref().map_or(false, |f| !f.is_empty()) {
        let var_name = &format!("{}{}", data_dir, topic.pic.clone().unwrap());
        let old_file = Path::new(var_name.as_str());
        let _new = topic
            .pic
            .clone()
            .unwrap()
            .replace(t, format!("/{}/", topic.id.unwrap()).as_str());
        let var_name = &format!("{}{}", data_dir, _new);
        let new_file = Path::new(var_name.as_str());
        let parent = new_file.parent().unwrap();
        if !parent.exists() {
            std::fs::create_dir_all(parent).unwrap();
        }
        fs::rename(old_file, new_file).unwrap();
        let filepath = new_file
            .file_name()
            .map_or(None, |f| f.to_str().map_or(None, |f| Some(f.to_string())))
            .unwrap_or_default();
        urls.push(filepath);
        topic.pic = Some(_new);
    }

    urls
}
//自动摘要
fn unescap_sum(topic: &mut Topic) {
    let mut content = topic.content.clone().unwrap_or_default();
    if topic.style == Some(1) {
        content = markdown::to_html(&content);
    }
    let regex = Regex::new(r#"(<.*?>)|(&.*?;)|([\s\r\t\n]*)"#).unwrap();
    let html = html_escape::decode_html_entities(content.as_str()).to_string();
    let result = regex.replace_all(&html, "").to_string();
    let min = min(100, result.len());
    let sum = result.chars().take(min).collect();
    topic.sum = Some(sum);
}

pub async fn update(topic: &mut Topic) -> Result<u64, Error> {
    let rb = AppContext::rbatis();
    if topic.sum.as_ref().map_or(true, |f| f.is_empty()) {
        unescap_sum(topic);
    }
    let imgs = rname_imgs(topic);
    let mut image_dir = image_dir();
    image_dir.push_str("/");
    image_dir.push_str(topic.id.unwrap().to_string().as_str());
    //检查删除的图片
    let mut read_dir = fs::read_dir(&image_dir).ok();
    for file in read_dir.iter_mut().flat_map(|f| f.into_iter()) {
        let path = &file.unwrap().path();
        let name = path.file_name().unwrap().to_str().unwrap().to_string();
        if !imgs.contains(&name) {
            fs::remove_file(path).unwrap_or_default();
        }
    }
    if imgs.is_empty() {
        fs::remove_dir(image_dir).unwrap_or_default();
    }
    let _ = Topic::update_by_column(rb, &topic, "id").await?;
    let topic = select_by_id(topic.id.unwrap()).await;
    search_service::save_document(&topic).unwrap();
    Ok(topic.id.unwrap())
}
pub async fn delete(topic: Topic) -> Result<u64, Error> {
    let rb = AppContext::rbatis().acquire_begin().await.unwrap();
    let mut tx = rb.defer_async(|mut tx| async move {
        if !tx.done {
            let _ = tx.rollback().await;
        }
    });
    let id = topic.id.unwrap();
    let _ = Reply::delete_by_column(&tx, "cid", id).await.unwrap();
    let _ = Topic::delete_by_column(&tx, "id", id).await?;
    //检查删除图片
    let mut image_dir = image_dir();
    image_dir.push_str("/");
    image_dir.push_str(id.to_string().as_str());
    let mut read_dir = fs::read_dir(&image_dir).ok();
    for file in read_dir.iter_mut().flat_map(|f| f.into_iter()) {
        let path = &file.unwrap().path();
        fs::remove_file(path).unwrap_or_default();
    }
    fs::remove_dir(image_dir).unwrap_or_default();
    search_service::delete(id);
    let _ = tx.commit().await;
    Ok(topic.id.unwrap())
}
pub async fn tags() -> Vec<String> {
    let rb = AppContext::rbatis();
    let query = domain::topic::select_tags(rb).await.unwrap();
    let vec: Vec<String> = query
        .iter()
        .flat_map(|f| f["tag"].as_str())
        .filter(|f| !f.is_empty())
        .flat_map(|f| f.split(",").into_iter())
        .map(str::to_string)
        .collect::<BTreeSet<String>>()
        .into_iter()
        .collect();
    vec
}
pub async fn select_by_id(id: u64) -> Topic {
    let topic = Topic::select_by_id(AppContext::rbatis(), id).await;
    let topic = topic.unwrap_or_default().first().unwrap().to_owned();
    return topic;
}

pub async fn rebuild_index() {
    let mut data_dir = utils::data_dir();
    data_dir.push_str("/index");
    if !Path::new(data_dir.as_str()).exists() {
        let data = Topic::select_all(AppContext::rbatis()).await.unwrap();
        search_service::init_index(data);
        log::info!("##rebuild index success")
    }
}
